#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "xml.h"

static void
xml_parseattrs(XMLParser *x)
{
	size_t namelen = 0, valuelen;
	int c, endsep, endname = 0, valuestart = 0;

	while ((c = GETNEXT()) != EOF) {
		if (isspace(c)) {
			if (namelen)
				endname = 1;
			continue;
		} else if (c == '?')
			; /* ignore */
		else if (c == '=') {
			x->name[namelen] = '\0';
			valuestart = 1;
			endname = 1;
		} else if (namelen && ((endname && !valuestart && isalpha(c)) || (c == '>' || c == '/'))) {
			/* attribute without value */
			x->name[namelen] = '\0';
			if (x->xmlattrstart)
				x->xmlattrstart(x, x->tag, x->taglen, x->name, namelen);
			if (x->xmlattr)
				x->xmlattr(x, x->tag, x->taglen, x->name, namelen, "", 0);
			if (x->xmlattrend)
				x->xmlattrend(x, x->tag, x->taglen, x->name, namelen);
			endname = 0;
			x->name[0] = c;
			namelen = 1;
		} else if (namelen && valuestart) {
			/* attribute with value */
			if (x->xmlattrstart)
				x->xmlattrstart(x, x->tag, x->taglen, x->name, namelen);

			valuelen = 0;
			if (c == '\'' || c == '"') {
				endsep = c;
			} else {
				endsep = ' '; /* isspace() */
				goto startvalue;
			}

			while ((c = GETNEXT()) != EOF) {
startvalue:
				if (c == '&') { /* entities */
					x->data[valuelen] = '\0';
					/* call data function with data before entity if there is data */
					if (valuelen && x->xmlattr)
						x->xmlattr(x, x->tag, x->taglen, x->name, namelen, x->data, valuelen);
					x->data[0] = c;
					valuelen = 1;
					while ((c = GETNEXT()) != EOF) {
						if (c == endsep || (endsep == ' ' && (c == '>' || isspace(c))))
							break;
						if (valuelen < sizeof(x->data) - 1)
							x->data[valuelen++] = c;
						else {
							/* entity too long for buffer, handle as normal data */
							x->data[valuelen] = '\0';
							if (x->xmlattr)
								x->xmlattr(x, x->tag, x->taglen, x->name, namelen, x->data, valuelen);
							x->data[0] = c;
							valuelen = 1;
							break;
						}
						if (c == ';') {
							x->data[valuelen] = '\0';
							if (x->xmlattrentity)
								x->xmlattrentity(x, x->tag, x->taglen, x->name, namelen, x->data, valuelen);
							valuelen = 0;
							break;
						}
					}
				} else if (c != endsep && !(endsep == ' ' && (c == '>' || isspace(c)))) {
					if (valuelen < sizeof(x->data) - 1) {
						x->data[valuelen++] = c;
					} else {
						x->data[valuelen] = '\0';
						if (x->xmlattr)
							x->xmlattr(x, x->tag, x->taglen, x->name, namelen, x->data, valuelen);
						x->data[0] = c;
						valuelen = 1;
					}
				}
				if (c == endsep || (endsep == ' ' && (c == '>' || isspace(c)))) {
					x->data[valuelen] = '\0';
					if (x->xmlattr)
						x->xmlattr(x, x->tag, x->taglen, x->name, namelen, x->data, valuelen);
					if (x->xmlattrend)
						x->xmlattrend(x, x->tag, x->taglen, x->name, namelen);
					break;
				}
			}
			namelen = endname = valuestart = 0;
		} else if (namelen < sizeof(x->name) - 1) {
			x->name[namelen++] = c;
		}
		if (c == '>') {
			break;
		} else if (c == '/') {
			x->isshorttag = 1;
			x->name[0] = '\0';
			namelen = 0;
		}
	}
}

void
xml_parse(XMLParser *x)
{
	size_t datalen, tagdatalen;
	int c, isend;

	while ((c = GETNEXT()) != EOF && c != '<')
		; /* skip until < */

	while (c != EOF) {
		if (c == '<') { /* parse tag */
			if ((c = GETNEXT()) == EOF)
				return;

			/* normal tag (open, short open, close), processing instruction. */
			x->tag[0] = c;
			x->taglen = 1;
			x->isshorttag = isend = 0;

			/* treat processing instruction as shorttag, don't strip "?" prefix. */
			if (c == '?') {
				x->isshorttag = 1;
			} else if (c == '/') {
				if ((c = GETNEXT()) == EOF)
					return;
				x->tag[0] = c;
				isend = 1;
			}

			while ((c = GETNEXT()) != EOF) {
				if (c == '/')
					x->isshorttag = 1; /* short tag */
				else if (c == '>' || isspace(c)) {
					x->tag[x->taglen] = '\0';
					if (isend) { /* end tag, starts with </ */
						if (x->xmltagend)
							x->xmltagend(x, x->tag, x->taglen, x->isshorttag);
						x->tag[0] = '\0';
						x->taglen = 0;
					} else {
						/* start tag */
						if (x->xmltagstart)
							x->xmltagstart(x, x->tag, x->taglen);
						if (isspace(c))
							xml_parseattrs(x);
						if (x->xmltagstartparsed)
							x->xmltagstartparsed(x, x->tag, x->taglen, x->isshorttag);
					}
					/* call tagend for shortform or processing instruction */
					if (x->isshorttag) {
						if (x->xmltagend)
							x->xmltagend(x, x->tag, x->taglen, x->isshorttag);
						x->tag[0] = '\0';
						x->taglen = 0;
					}
					break;
				} else if (x->taglen < sizeof(x->tag) - 1)
					x->tag[x->taglen++] = c; /* NOTE: tag name truncation */
			}
		} else {
			/* parse tag data */
			datalen = 0;
			if (x->xmldatastart)
				x->xmldatastart(x);
			while ((c = GETNEXT()) != EOF) {
				if (c == '&') {
					if (datalen) {
						x->data[datalen] = '\0';
						if (x->xmldata)
							x->xmldata(x, x->data, datalen);
					}
					x->data[0] = c;
					datalen = 1;
					while ((c = GETNEXT()) != EOF) {
						if (c == '<')
							break;
						if (datalen < sizeof(x->data) - 1)
							x->data[datalen++] = c;
						else {
							/* entity too long for buffer, handle as normal data */
							x->data[datalen] = '\0';
							if (x->xmldata)
								x->xmldata(x, x->data, datalen);
							x->data[0] = c;
							datalen = 1;
							break;
						}
						if (c == ';') {
							x->data[datalen] = '\0';
							if (x->xmldataentity)
								x->xmldataentity(x, x->data, datalen);
							datalen = 0;
							break;
						}
					}
				} else if (c != '<') {
					if (datalen < sizeof(x->data) - 1) {
						x->data[datalen++] = c;
					} else {
						x->data[datalen] = '\0';
						if (x->xmldata)
							x->xmldata(x, x->data, datalen);
						x->data[0] = c;
						datalen = 1;
					}
				}
				if (c == '<') {
					x->data[datalen] = '\0';
					if (x->xmldata && datalen)
						x->xmldata(x, x->data, datalen);
					if (x->xmldataend)
						x->xmldataend(x);
					break;
				}
			}
		}
	}
}
